package com.example.shuiyin.service.impl;

import com.example.shuiyin.dto.MultipartUploadDto;
import com.example.shuiyin.dto.PartUploadResult;
import com.example.shuiyin.dto.UploadCompleteResult;
import com.example.shuiyin.entity.FileInfo;
import com.example.shuiyin.entity.MultipartUpload;
import com.example.shuiyin.exception.AppException;
import com.example.shuiyin.exception.ErrorCode;
import com.example.shuiyin.mapper.MultipartUploadMapper;
import com.example.shuiyin.service.FileService;
import com.example.shuiyin.service.MinioStorageService;
import com.example.shuiyin.service.MultipartUploadService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class MultipartUploadServiceImpl implements MultipartUploadService {

    private final MultipartUploadMapper multipartUploadMapper;
    private final MinioStorageService storageService;
    private final FileService fileService;
    
    @Override
    @Transactional
    public MultipartUploadDto initMultipartUpload(Long userId, String fileName, Long fileSize, String contentType, Integer totalParts) {
        // 生成唯一上传ID
        String uploadId = UUID.randomUUID().toString().replace("-", "");
        String fileId = UUID.randomUUID().toString().replace("-", "");
        
        // 创建上传记录
        MultipartUpload upload = new MultipartUpload();
        upload.setUploadId(uploadId);
        upload.setUserId(userId);
        upload.setFileId(fileId);
        upload.setFileName(fileName);
        upload.setFileSize(fileSize);
        upload.setContentType(contentType);
        upload.setTotalParts(totalParts);
        upload.setCompletedParts(0);
        upload.setStatus("IN_PROGRESS");
        upload.setCreatedAt(LocalDateTime.now());
        upload.setUpdatedAt(LocalDateTime.now());
        
        multipartUploadMapper.insert(upload);
        
        // 返回DTO
        return MultipartUploadDto.builder()
                .uploadId(uploadId)
                .fileId(fileId)
                .fileName(fileName)
                .fileSize(fileSize)
                .contentType(contentType)
                .totalParts(totalParts)
                .completedParts(0)
                .status("IN_PROGRESS")
                .createdAt(upload.getCreatedAt())
                .build();
    }
    
    @Override
    @Transactional
    public PartUploadResult uploadPart(Long userId, String uploadId, Integer partNumber, MultipartFile partFile) {
        // 获取上传记录
        MultipartUpload upload = multipartUploadMapper.selectByUploadIdAndUserId(uploadId, userId);
        if (upload == null) {
            throw new AppException("上传记录不存在", ErrorCode.RESOURCE_NOT_FOUND);
        }
        
        if (!"IN_PROGRESS".equals(upload.getStatus())) {
            throw new AppException("上传已完成或已取消", ErrorCode.INVALID_STATE);
        }
        
        if (partNumber < 1 || partNumber > upload.getTotalParts()) {
            throw new AppException("无效的分片编号", ErrorCode.INVALID_PARAMETER);
        }
        
        try {
            // 构建分片存储路径
            String partObjectName = String.format("multipart/%s/%s/part%d", 
                    userId, upload.getUploadId(), partNumber);
            
            // 上传分片到存储
            String partUrl = storageService.uploadFile(partFile, partObjectName);
            
            // 更新已完成分片数
            multipartUploadMapper.incrementCompletedParts(uploadId);
            
            // 更新上传记录
            upload.setCompletedParts(upload.getCompletedParts() + 1);
            upload.setUpdatedAt(LocalDateTime.now());
            multipartUploadMapper.updateById(upload);
            
            // 检查是否所有分片都已上传完成
            boolean completed = upload.getCompletedParts() + 1 >= upload.getTotalParts();
            
            return PartUploadResult.builder()
                    .uploadId(uploadId)
                    .partNumber(partNumber)
                    .etag(partUrl) // 使用URL作为ETag
                    .completed(completed)
                    .completedParts(upload.getCompletedParts() + 1)
                    .totalParts(upload.getTotalParts())
                    .build();
        } catch (IOException e) {
            log.error("上传分片失败: {}", e.getMessage(), e);
            throw new AppException("上传分片失败: " + e.getMessage(), ErrorCode.FILE_UPLOAD_ERROR);
        }
    }
    
    @Override
    @Transactional
    public UploadCompleteResult completeUpload(Long userId, String uploadId) {
        // 获取上传记录
        MultipartUpload upload = multipartUploadMapper.selectByUploadIdAndUserId(uploadId, userId);
        if (upload == null) {
            throw new AppException("上传记录不存在", ErrorCode.RESOURCE_NOT_FOUND);
        }
        
        if (!"IN_PROGRESS".equals(upload.getStatus())) {
            throw new AppException("上传已完成或已取消", ErrorCode.INVALID_STATE);
        }
        
        // 检查分片是否全部上传完成
        if (upload.getCompletedParts() < upload.getTotalParts()) {
            throw new AppException("分片上传未完成", ErrorCode.INVALID_STATE);
        }
        
        try {
            // TODO: 在生产环境中，这里应该实现合并分片的逻辑
            // 简化示例: 直接将上传标记为完成
            
            // 更新上传状态
            upload.setStatus("COMPLETED");
            upload.setUpdatedAt(LocalDateTime.now());
            multipartUploadMapper.updateById(upload);
            
            // 创建文件记录
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileId(upload.getFileId());
            fileInfo.setUserId(userId);
            fileInfo.setFileName(upload.getFileName());
            fileInfo.setFilePath("multipart/" + userId + "/" + uploadId + "/merged");
            fileInfo.setFileSize(upload.getFileSize());
            fileInfo.setContentType(upload.getContentType());
            fileInfo.setStatus("ACTIVE");
            fileInfo.setUploadTime(LocalDateTime.now());
            fileInfo.setLastAccessTime(LocalDateTime.now());
            
            // 保存文件信息
            fileService.saveFileInfo(fileInfo);
            
            return UploadCompleteResult.builder()
                    .fileId(upload.getFileId())
                    .fileName(upload.getFileName())
                    .fileSize(upload.getFileSize())
                    .url("/api/file/" + upload.getFileId())
                    .build();
        } catch (Exception e) {
            log.error("完成上传失败: {}", e.getMessage(), e);
            throw new AppException("完成上传失败: " + e.getMessage(), ErrorCode.FILE_UPLOAD_ERROR);
        }
    }
    
    @Override
    @Transactional
    public boolean abortUpload(Long userId, String uploadId) {
        // 获取上传记录
        MultipartUpload upload = multipartUploadMapper.selectByUploadIdAndUserId(uploadId, userId);
        if (upload == null) {
            throw new AppException("上传记录不存在", ErrorCode.RESOURCE_NOT_FOUND);
        }
        
        if (!"IN_PROGRESS".equals(upload.getStatus())) {
            // 已经完成或取消，无需操作
            return true;
        }
        
        try {
            // 更新上传状态
            upload.setStatus("ABORTED");
            upload.setUpdatedAt(LocalDateTime.now());
            multipartUploadMapper.updateById(upload);
            
            // 删除已上传的分片
            for (int i = 1; i <= upload.getCompletedParts(); i++) {
                String partObjectName = String.format("multipart/%s/%s/part%d", 
                        userId, upload.getUploadId(), i);
                try {
                    storageService.deleteObject(partObjectName);
                } catch (Exception e) {
                    log.warn("删除分片失败: {}", partObjectName, e);
                    // 继续删除其他分片
                }
            }
            
            return true;
        } catch (Exception e) {
            log.error("取消上传失败: {}", e.getMessage(), e);
            throw new AppException("取消上传失败: " + e.getMessage(), ErrorCode.INTERNAL_ERROR);
        }
    }
    
    @Override
    public MultipartUploadDto getUploadStatus(Long userId, String uploadId) {
        MultipartUpload upload = multipartUploadMapper.selectByUploadIdAndUserId(uploadId, userId);
        if (upload == null) {
            throw new AppException("上传记录不存在", ErrorCode.RESOURCE_NOT_FOUND);
        }
        
        return MultipartUploadDto.builder()
                .uploadId(upload.getUploadId())
                .fileId(upload.getFileId())
                .fileName(upload.getFileName())
                .fileSize(upload.getFileSize())
                .contentType(upload.getContentType())
                .totalParts(upload.getTotalParts())
                .completedParts(upload.getCompletedParts())
                .status(upload.getStatus())
                .createdAt(upload.getCreatedAt())
                .build();
    }
    
    @Override
    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    @Transactional
    public int cleanupExpiredUploads() {
        List<MultipartUpload> expiredUploads = multipartUploadMapper.selectExpiredUploads();
        int count = 0;
        
        for (MultipartUpload upload : expiredUploads) {
            try {
                // 更新状态
                upload.setStatus("EXPIRED");
                multipartUploadMapper.updateById(upload);
                
                // 删除已上传的分片
                for (int i = 1; i <= upload.getCompletedParts(); i++) {
                    String partObjectName = String.format("multipart/%s/%s/part%d", 
                            upload.getUserId(), upload.getUploadId(), i);
                    try {
                        storageService.deleteObject(partObjectName);
                    } catch (Exception e) {
                        log.warn("删除过期分片失败: {}", partObjectName, e);
                    }
                }
                
                count++;
            } catch (Exception e) {
                log.error("清理过期上传失败: {}", upload.getUploadId(), e);
            }
        }
        
        log.info("成功清理{}个过期上传", count);
        return count;
    }
} 